cover photo
Melbourne, 09 June 2023
updated on Oct 25, 2024

What’s the Ember equivalent of a React Ref?

In React, it's really common in front-end development to pass a ref to a child component, either through ref forwarding or by creating a ref with something like const ref = useRef(), then passing it down. This allows the parent component to directly access the child’s methods and properties.

In Ember, however, things work a bit differently. Instead of using refs, Ember provides a did-insert modifier that gives you access to the inserted HTML element. But it only works with native HTML elements. Here’s an example of how it looks:

<div {{did-insert this.handleInsert}}>
  <!-- some HTML content -->
</div>
parent-component.hbs
import Component from '@glimmer/component';
import { action } from '@ember/object';

interface Args {
  // list of arguments
}

export default class ParentComponent extends Component<Args> {
  @action
  handleInsert(element: HTMLDivElement) {
    // do something with 'element'
  }
}
parent-component.ts

Now, let’s say we want to render another Ember component inside ParentComponent instead of a div, like this:

<!-- 
  this.handleInsert won’t ever be called because 
  did-insert only works with native HTML elements.
-->
<ChildComponent {{did-insert this.handleInsert}} />
parent-component.hbs

The solution is to pass a ref argument to the child component and include a reference to the child component in its constructor.

import Component from '@glimmer/component';

interface Args {
  ref?: (component: ChildComponent)
  // list of other arguments
}

export default class ChildComponent extends Component<Args> {
  constructor(owner: unknown, args: Args) {
    super(owner, args);
	  
    // Call 'ref' if it is provided
    this.args.ref?.(this);
  }
	
  public doSomething() {}
}
child-component.ts

Next, update the parent component accordingly:

<!-- 
  'this.handleInsert' won't be ever called, 
  because 'did-insert' only works for native HTML elements
-->
<ChildComponent @ref={{this.handleChildRef}} />
parent-component.hbs
import Component from '@glimmer/component';
import { action} from '@ember/object';
import { tracked } from '@glimmer/tracking';

interface Args {
  // list of arguments
}

export default class ParentComponent extends Component<Args> {
  @tracked childRef?: ChildComponent;

  @action
  handleChildRef(component: ChildComponent) {
    this.childRef = component;
  }
	
  /**
   * You can call methods of the child component from anywhere in parent component.
   */
  private someMethod() {
    this.childRef?.doSomething();
  }
}
parent-component.ts

By doing this, you expose all the public methods and properties of the child component to the parent. Since there may be some you don’t want to expose, it’s a good practice to always use the public, protected, and private keywords to ensure only what needs to be accessible is exposed.

If you want more control and prefer to pass a custom set of methods and properties to the parent, you can modify the code as shown below. This is the Ember equivalent of React’s useImperativeHandle hook.

import Component from '@glimmer/component';

interface Args {
  ref?: (component: ChildComponent)
  // list of other arguments
}

export type ChildComponentRef = {
  doSomething: () => void;
  anotherMethod: () => void;
}

export default class ChildComponent extends Component<Args> {
  constructor(owner: unknown, args: Args) {
    super(owner, args);
	  
    // Call 'ref' if it is provided
    this.args.ref?.({
	    doSomething: this.doSomething.bind(this),
	    anotherMethod: () => {
	      // do some stuff
      }
    });
  }
	
  public doSomething() {}
}
child-component.ts

And the parent component:

import Component from '@glimmer/component';
import { action} from '@ember/object';
import { tracked } from '@glimmer/tracking';

interface Args {
  // list of arguments
}

export default class ParentComponent extends Component<Args> {
  @tracked childRef?: ChildComponentRef;

  @action
  handleChildRef(component: ChildComponentRef) {
    this.childRef = component;
  }
	
  /**
   * You can call methods of the child component from anywhere in parent component.
   */
  private someMethod() {
    this.childRef?.doSomething();
    thid.childRef?.anotherMethod();
  }
}
parent-component.ts

Job done! Thanks for reading.

© 2025 Ramin Yavari. All rights reserved.